package com.lvmama.java.rhino.spark.utils.copy;

import net.sf.cglib.beans.BeanCopier;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by chenrui on 16/1/13.
 */
public class CachedBeanCopier implements ApplicationContextAware, InitializingBean {

    private static final Logger logger = LoggerFactory.getLogger(CachedBeanCopier.class);

    private final Map<String, BeanCopier> BEAN_COPIER = new HashMap<String, BeanCopier>();
    private final Map<String, BeanCopierConverter> CONVERTERS = new HashMap<String, BeanCopierConverter>();

    private ApplicationContext ac;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.ac = applicationContext;
    }

    /**
     * 初始化转换器和属性拷贝对象
     * @throws Exception
     */
    @Override
    public void afterPropertiesSet() throws Exception {
        Map<String, BeanCopierConverter> beanMap = ac.getBeansOfType(BeanCopierConverter.class);
        for (String beanName : beanMap.keySet()) {
            BeanCopierConverter converter = beanMap.get(beanName);
            addConverter(converter);
        }
    }

    /**
     * 添加转换器
     * @param converter 转换器
     */
    public void addConverter(BeanCopierConverter converter) {
        String key = genKey(converter.getSrcClazz(), converter.getTargetClazz());
        CONVERTERS.put(key, converter);

        BeanCopier copier = BeanCopier.create(converter.getSrcClazz(), converter.getTargetClazz(), true);
        BEAN_COPIER.put(key, copier);
    }

    /**
     * 生成KEY
     * @param srcClazz 原class
     * @param destClazz 目标class
     * @return
     */
    private static String genKey(Class<?> srcClazz, Class<?> destClazz) {
        return srcClazz.getName() + destClazz.getName();
    }

    /**
     * 拷贝属性
     * @param srcObject 原对象
     * @param targetObject 目标对象
     */
    public void copy(Object srcObject, Object targetObject) {
        if(srcObject == null || targetObject == null) {
            return;
        }

        String key = genKey(srcObject.getClass(), targetObject.getClass()); //生成KEY

        BeanCopier copier;
        if(BEAN_COPIER.containsKey(key)) { //缓存存在
            copier = BEAN_COPIER.get(key);
        } else { //缓存不存在
            copier = BeanCopier.create(srcObject.getClass(), targetObject.getClass(), CONVERTERS.containsKey(key));
            BEAN_COPIER.put(key, copier);
        }

        BeanCopierConverter converter = CONVERTERS.get(key);
        copier.copy(srcObject, targetObject, converter);

        if(converter != null && MapUtils.isNotEmpty(converter.getPropertyMap())){
            for(String left : converter.getPropertyMap().keySet()) {
                String right = converter.getPropertyMap().get(left);

                try {
                    Field leftField = getDeclaredField(converter.getSrcClazz(), left, true);
                    Field rightField = getDeclaredField(converter.getTargetClazz(), right, true);

                    if(leftField == null || rightField == null) {
                        continue;
                    }

                    Object src = leftField.get(srcObject);
                    if(src == null) {
                        continue;
                    }

                    Class srcClazz = src.getClass();
                    Object transferBean;
                    if(src instanceof List) {
                        ParameterizedType targetType = (ParameterizedType)rightField.getGenericType();
                        Class targetClazz = (Class) targetType.getActualTypeArguments()[0];

                        transferBean = copyList((List<?>) src, targetClazz);
                    } else if (srcClazz == Byte.class || srcClazz == Short.class || srcClazz == Integer.class
                            || srcClazz == Long.class || srcClazz == Float.class || srcClazz == Double.class
                            || srcClazz == Character.class || srcClazz == Boolean.class || srcClazz == String.class){
                        transferBean = src;
                    } else {
                        transferBean = ((Class) rightField.getGenericType()).newInstance();
                        copy(src, transferBean);
                    }
                    rightField.set(targetObject, transferBean);
                } catch (Exception e) {
                    logger.error("BEAN拷贝过程中,属性转换 [", left + "] 调用失败", e);
                }

            }
        }
    }

    private Field getDeclaredField(Class<?> cls, String fieldName, boolean forceAccess) {
        for(Class clazz = cls; clazz != null; clazz = clazz.getSuperclass()) {
            Field field = FieldUtils.getDeclaredField(clazz, fieldName, forceAccess);
            if(field != null) {
                return field;
            }
        }
        return null;
    }

    public <M, N> List<N> copyList(List<M> src, Class<N> targetClazz) {
        if(CollectionUtils.isEmpty(src)) {
            return null;
        }

        List<N> target = new ArrayList<N>();
        for (M m : src) {
            target.add(copy(m, targetClazz));
        }
        return target;
    }

    public <T> T copy(Object srcObject, Class<T> targetClazz) {
        try {
            T targetObject = targetClazz.newInstance();
            copy(srcObject, targetObject);
            return targetObject;
        } catch (Exception e) {
            logger.error("BEAN拷贝异常", e);
            return null;
        }
    }
}
